package com.ihr360.payroll.service.staff.validator;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.ihr360.commons.exception.Ihr360Exception;
import com.ihr360.commons.exception.Ihr360RuntimeException;
import com.ihr360.commons.lang.CollectionUtils;
import com.ihr360.commons.lang.DateUtils;
import com.ihr360.commons.lang.StringUtils;
import com.ihr360.commons.model.meta.table.DynamicCell;
import com.ihr360.excel.vo.CustomSpreadSheetValidateResult;
import com.ihr360.excel.vo.SpreadSheetValidatorError;
import com.ihr360.payroll.entity.staff.SalaryProfileEntity;
import com.ihr360.payroll.entity.staff.StaffSalaryProfileEntity;
import com.ihr360.payroll.request.SalaryProfileFieldRequest;
import com.ihr360.payroll.request.SalaryProfileTypeItem;
import com.ihr360.payroll.request.StaffPayrollInfoRequest;
import com.ihr360.payroll.request.StaffPayrollInfosRequest;
import com.ihr360.payroll.service.staff.StaffPayrollInfoService;
import com.ihr360.payroll.type.SalaryProfileChangeType;
import com.ihr360.validation.ValidationUtils;
import com.ihr360.validation.Validator;
import org.apache.commons.collections.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author richey
 */
@Component
public class StaffPayrollInfosValidator implements Validator<StaffPayrollInfosRequest> {


    @Autowired
    private StaffPayrollInfoService staffPayrollInfoService;

    @Override
    public boolean supports(Class<?> clazz) {
        return StaffPayrollInfosRequest.class.equals(clazz);
    }

    @Override
    public void validate(StaffPayrollInfosRequest target) {
        validate(target, null);
    }


    public void validate(StaffPayrollInfosRequest target, CustomSpreadSheetValidateResult spreadSheetValidateResult) {

        List<SpreadSheetValidatorError> errors = new ArrayList<>();
        if (spreadSheetValidateResult != null) {
            spreadSheetValidateResult.setErrors(errors);
            importDataHandler(target);
        }


        List<StaffPayrollInfoRequest> staffPayrollInfoRequests = Lists.newArrayList();
        if (spreadSheetValidateResult == null) {
            staffPayrollInfoRequests = checkParamValid(target);
        } else {
            staffPayrollInfoRequests = target.getPayrollInfos();
        }
        /**
         * 同一个员工
         *  同一个月相同调整类型只允许有一条记录　　　ok
         *  同一个月相同调整原因的生效时间必须相同   ok
         *  同一天只能有一条修改记录            　ok
         */

        for (StaffPayrollInfoRequest staffPayrollInfo : staffPayrollInfoRequests) {
            List<SalaryProfileTypeItem> salaryProfiles = staffPayrollInfo.getTypeItems();
            ValidationUtils.rejectIfEmpty(salaryProfiles, null, String.join("", "员工", staffPayrollInfo.getStaffName(), "不存在薪资档案字段数据！"));
            for (SalaryProfileTypeItem salaryProfile : salaryProfiles) {
                if (salaryProfile.getChangeType() == null) {
                    if (spreadSheetValidateResult != null) {
                        addSpreadSheetValidateError(errors, Sets.newHashSet(salaryProfile.getRowNum()), null, "调整原因不能为空");
                        continue;
                    } else {
                        throw new Ihr360Exception(null, "调整原因不能为空！");
                    }
                }
            }

            //是否有当前生效数据
            boolean hasEffectiveData = salaryProfiles.stream().filter(s -> s.getEffectiveAt() != null
                    && (DateUtils.isSameDay(s.getEffectiveAt(), new Date())
                    || s.getEffectiveAt().before(DateUtils.getMinDateInDay(new Date()))))
                    .count() > 0;

            if (!hasEffectiveData) {
                if (spreadSheetValidateResult != null) {
                    Set<Integer> errorRows = salaryProfiles.stream().filter(s -> s.getRowNum() != null)
                            .map(SalaryProfileTypeItem::getRowNum).collect(Collectors.toSet());
                    addSpreadSheetValidateError(errors, errorRows, null, "不存在当前生效数据");
                    continue;
                } else {
                    throw new Ihr360Exception(null, String.join("", staffPayrollInfo.getStaffName(), "不存在生效薪资档案！"));
                }
            }

            //月，调整类型
            Map<String, SalaryProfileChangeType> dateYMChangetypeMap = Maps.newHashMap();
            Map<String, List<SalaryProfileTypeItem>> dateYMItemsMap = Maps.newHashMap();
            Map<String, List<Integer>> effectiveDataRowMap = Maps.newHashMap();

            for (SalaryProfileTypeItem salaryProfile : salaryProfiles) {
                Date effectiveAt = salaryProfile.getEffectiveAt();
                ValidationUtils.rejectIfEmpty(effectiveAt, null, String.join("", staffPayrollInfo.getStaffName(), "生效时间不能为空！"));
                String effectiveAtStr = DateUtils.formatDateStr(effectiveAt, DateUtils.STANDARD_DATE_FORMAT_YMD);
                List<Integer> rowNums = effectiveDataRowMap.get(effectiveAtStr);
                if (CollectionUtils.isNotEmpty(rowNums)) {
                    if (spreadSheetValidateResult == null) {
                        throw new Ihr360Exception(null, "同一天只能有一条薪资记录！");
                    } else {
                        rowNums.add(salaryProfile.getRowNum());
                        continue;
                    }
                } else {
                    rowNums = Lists.newArrayList();
                    effectiveDataRowMap.put(effectiveAtStr, rowNums);
                }
                rowNums.add(salaryProfile.getRowNum());


                ValidationUtils.rejectIfEmpty(salaryProfile.getChangeType(), null, String.join("", staffPayrollInfo.getStaffName(), "调整原因不能为空！"));
                ValidationUtils.rejectIfEmpty(salaryProfile.getEffectiveAt(), null, String.join("", staffPayrollInfo.getStaffName(), "生效时间不能为空！"));
                if (spreadSheetValidateResult == null) {
                    ValidationUtils.rejectIfEmpty(salaryProfile.getProfileFields(), null, String.join("", staffPayrollInfo.getStaffName(), salaryProfile.getChangeType().getTitle(), "不存在薪资字段！"));
                } else {
                    if (CollectionUtils.isEmpty(salaryProfile.getProfileFields())) {
                        addSpreadSheetValidateError(errors, Sets.newHashSet(salaryProfile.getRowNum()), null, "不存在薪资字段");
                        continue;
                    }
                }
                List<SalaryProfileFieldRequest> salaryProfileFields = salaryProfile.getProfileFields();
                salaryProfileFields.forEach(salaryProfileField -> {
                    ValidationUtils.rejectIfEmpty(salaryProfileField.getCode(), null, String.join("", staffPayrollInfo.getStaffName(), "code不能为空！"));
                });
                String effectiveAtYMStr = DateUtils.formatDateStr(salaryProfile.getEffectiveAt(), DateUtils.STANDARD_DATE_FORMAT_YM1);
                List<SalaryProfileTypeItem> ymItems = dateYMItemsMap.get(effectiveAtYMStr);
                if (CollectionUtils.isEmpty(ymItems)) {
                    ymItems = Lists.newArrayList();
                    dateYMItemsMap.put(effectiveAtYMStr, ymItems);
                }
                ymItems.add(salaryProfile);

                if (salaryProfile.getChangeType().equals(dateYMChangetypeMap.get(effectiveAtYMStr))) {
                    if (spreadSheetValidateResult == null) {
                        throw new Ihr360Exception(null, String.join("", staffPayrollInfo.getStaffName(), salaryProfile.getChangeType().getTitle(), "(", effectiveAtYMStr, ")", "存在多条记录！"));
                    }
                } else {
                    dateYMChangetypeMap.put(effectiveAtYMStr, salaryProfile.getChangeType());
                }
            }

            if (MapUtils.isNotEmpty(effectiveDataRowMap) && spreadSheetValidateResult != null) {

                effectiveDataRowMap.forEach((dateStr, rows) -> {
                    if (rows.size() > 1) {
                        addSpreadSheetValidateError(errors, Sets.newHashSet(rows), null, "同一天只能有一条薪资记录！");
                    }
                });

            }

            if (MapUtils.isNotEmpty(dateYMItemsMap)) {
                dateYMItemsMap.forEach((dateYMStr, items) -> {
                    if (items.size() > 1) {
                        Map<SalaryProfileChangeType, List<SalaryProfileTypeItem>> typeProfileMap = items.stream().collect(Collectors.groupingBy(SalaryProfileTypeItem::getChangeType));
                        typeProfileMap.forEach((type, typeItems) -> {
                            if (CollectionUtils.isNotEmpty(typeItems) && typeItems.size() > 1) {
                                typeItems.forEach(typeItem -> {
                                    if (typeItem.getRowNum() != null) {
                                        addSpreadSheetValidateError(errors, Sets.newHashSet(typeItem.getRowNum()), null, String.join("", dateYMStr, "存在多条", type.getTitle(), "类型的数据"));
                                    }
                                });
                            }
                        });
                    }
                });
            }
        }
    }


    private void addSpreadSheetValidateError(final List<SpreadSheetValidatorError> errors, Set<Integer> rowNums, Integer colNum, String errorMsg) {
        for (Integer rowNum : rowNums) {
            SpreadSheetValidatorError error = new SpreadSheetValidatorError();
            error.setMsg(errorMsg);
            error.setRowNum(rowNum);
            error.setColNum(colNum);
            errors.add(error);
        }
    }


    private List<StaffPayrollInfoRequest> checkParamValid(StaffPayrollInfosRequest target) {
        if (target == null) {
            throw new Ihr360RuntimeException("请求参数异常，参数为空！");
        }
        if (org.apache.commons.collections.CollectionUtils.isEmpty(target.getPayrollInfos())) {
            throw new Ihr360RuntimeException("请求参数异常，员工薪资档案数据不能为空！");
        }
        List<StaffPayrollInfoRequest> staffPayrollInfoRequests = target.getPayrollInfos();

        long emptyStaffIdCount = staffPayrollInfoRequests.stream()
                .filter(s -> StringUtils.isEmpty(s.getStaffId()) || StringUtils.isEmpty(s.getStaffName()))
                .count();
        if (emptyStaffIdCount > 0) {
            throw new Ihr360RuntimeException("请求参数异常，员工id和员工姓名不能为空!");
        }
        return staffPayrollInfoRequests;
    }

    /**
     * 处理导入数据
     * 删除（同一员工）导入数据最早生效时间之后的数据
     *
     * @param target
     * @param
     */
    private void importDataHandler(StaffPayrollInfosRequest target) {

        Set<String> staffIds = target.getPayrollInfos().stream().map(StaffPayrollInfoRequest::getStaffId).collect(Collectors.toSet());
        List<StaffSalaryProfileEntity> existStaffSalaryProfiles = staffPayrollInfoService.getSomeStaffsPayrollInfos(Lists.newArrayList(staffIds));

        //本次请求的员工薪资数据
        Map<String, StaffPayrollInfoRequest> payrollInfosMap = target.getPayrollInfos().stream().collect(Collectors.toMap(StaffPayrollInfoRequest::getStaffId, Function.identity()));

        if (CollectionUtils.isNotEmpty(existStaffSalaryProfiles)) {
            //已经存在的员工薪资数据
            Map<String, List<StaffSalaryProfileEntity>> existStaffIdProfileMap = existStaffSalaryProfiles.stream().collect(Collectors.groupingBy(s -> s.getStaff().getId()));

            existStaffIdProfileMap.forEach((staffId, profiles) -> {

                StaffPayrollInfoRequest requestProfileSalary = payrollInfosMap.get(staffId);
                Date minRequestEffectiveDate = null;
                if (requestProfileSalary != null && CollectionUtils.isNotEmpty(requestProfileSalary.getTypeItems())) {
                    minRequestEffectiveDate = requestProfileSalary.getTypeItems().stream().map(SalaryProfileTypeItem::getEffectiveAt).min(Comparator.naturalOrder()).get();
                    minRequestEffectiveDate = DateUtils.getMaxDateInDay(minRequestEffectiveDate);
                }

                StaffPayrollInfoRequest staffPayrollInfoRequest = new StaffPayrollInfoRequest();

                staffPayrollInfoRequest.setStaffId(staffId);
                List<SalaryProfileTypeItem> typeItems = Lists.newArrayList();
                staffPayrollInfoRequest.setTypeItems(typeItems);


                for (StaffSalaryProfileEntity profile : profiles) {

                    //如果是同一天，则覆盖所有薪资项目
                    boolean isSameDay = minRequestEffectiveDate != null && DateUtils.isSameDay(profile.getSalaryProfile().getEffectiveAt(), minRequestEffectiveDate);
                    if (isSameDay) {
                        for (SalaryProfileTypeItem item : requestProfileSalary.getTypeItems()) {
                            if (!DateUtils.isSameDay(item.getEffectiveAt(), minRequestEffectiveDate)) {
                                continue;
                            }
                            List<SalaryProfileFieldRequest> requestProfileFields = item.getProfileFields();
                            Map<String, SalaryProfileFieldRequest> requestProfieMap = requestProfileFields.stream().collect(Collectors.toMap(s -> s.getCode(), Function.identity()));

                            Map<String, DynamicCell> exitstItems = profile.getSalaryProfile().getWrapper().getItems();
                            exitstItems.forEach((code, cell) -> {
                                SalaryProfileFieldRequest salaryProfileFieldRequest = requestProfieMap.get(code);
                                if (salaryProfileFieldRequest == null) {
                                    salaryProfileFieldRequest = new SalaryProfileFieldRequest();
                                    salaryProfileFieldRequest.setChangeType(item.getChangeType());
                                    salaryProfileFieldRequest.setAdjustManually(true);
                                    salaryProfileFieldRequest.setEffectiveAt(item.getEffectiveAt());
                                    salaryProfileFieldRequest.setCode(code);
                                    salaryProfileFieldRequest.setValue(null);
                                    requestProfileFields.add(salaryProfileFieldRequest);
                                }
                            });
                        }
                        continue;
                    }


                    //删除导入数据最早生效时间之后的数据
                    boolean isSameOrAfterProfile = minRequestEffectiveDate != null && (DateUtils.isSameDay(profile.getSalaryProfile().getEffectiveAt(), minRequestEffectiveDate) || minRequestEffectiveDate.before(profile.getSalaryProfile().getEffectiveAt()));
                    if (isSameOrAfterProfile) {
                        continue;
                    }

                    SalaryProfileTypeItem rowItem = new SalaryProfileTypeItem();
                    typeItems.add(rowItem);

                    SalaryProfileEntity salaryProfileEntity = profile.getSalaryProfile();
                    Map<String, DynamicCell> items = salaryProfileEntity.getWrapper().getItems();
                    if (MapUtils.isEmpty(items)) {
                        continue;
                    }
                    rowItem.setChangeType(salaryProfileEntity.getChangeType());
                    rowItem.setEffectiveAt(salaryProfileEntity.getEffectiveAt());
                    List<SalaryProfileFieldRequest> rowSalaryProfileFields = Lists.newArrayList();
                    rowItem.setProfileFields(rowSalaryProfileFields);

                    items.forEach((code, dynamicCell) -> {
                        SalaryProfileFieldRequest salaryProfileFieldRequest = new SalaryProfileFieldRequest();
                        rowSalaryProfileFields.add(salaryProfileFieldRequest);
                        salaryProfileFieldRequest.setChangeType(salaryProfileEntity.getChangeType());
                        salaryProfileFieldRequest.setEffectiveAt(salaryProfileEntity.getEffectiveAt());
                        salaryProfileFieldRequest.setCode(code);
                        salaryProfileFieldRequest.setValue(dynamicCell.getCellValue());
                    });
                }
                if (CollectionUtils.isNotEmpty(typeItems)) {
                    StaffPayrollInfoRequest request = payrollInfosMap.get(staffId);
                    if (request == null) {
                        request = staffPayrollInfoRequest;
                        payrollInfosMap.put(staffId, request);
                    }
                    request.getTypeItems().addAll(typeItems);
                }

            });
        }
    }

}
